package com.flow.framework.web.advice;

import com.flow.framework.base.constant.FrameworkBaseConstant;
import com.flow.framework.base.properties.FrameworkBaseConfigProperties;
import com.flow.framework.base.properties.component.RequestConfigProperties;
import com.flow.framework.base.properties.component.ResponseConfigProperties;
import com.flow.framework.common.json.JsonObject;
import com.flow.framework.common.util.io.IoUtil;
import com.flow.framework.common.util.verify.VerifyUtil;
import com.flow.framework.core.constant.FrameworkCoreConstant;
import com.flow.framework.core.response.Response;
import com.flow.framework.core.service.properties.ISystemConfigPropertiesService;
import com.flow.framework.web.constant.FrameworkWebConstant;
import com.flow.framework.web.context.RequestResponseContext;
import com.flow.framework.web.helper.AccessLogHelper;
import com.flow.framework.web.service.web.IResponseBodyAdviceService;
import com.flow.framework.web.util.HttpServletUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * 响应信息统一封装
 *
 * @author luoguopiao
 * @version 0.0.1
 * @date 2022/12/24
 */
@Slf4j
@ControllerAdvice
public class WebResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    private final List<IResponseBodyAdviceService> responseBodyAdviceServices;

    private final String contextPath;

    private final FrameworkBaseConfigProperties frameworkBaseConfigProperties;

    private final AccessLogHelper accessLogHelper;

    public WebResponseBodyAdvice(List<IResponseBodyAdviceService> responseBodyAdviceServices,
                                 ISystemConfigPropertiesService systemConfigPropertiesService,
                                 FrameworkBaseConfigProperties frameworkBaseConfigProperties,
                                 AccessLogHelper accessLogHelper) {
        this.responseBodyAdviceServices = responseBodyAdviceServices;
        this.contextPath = systemConfigPropertiesService.getConfigValue(FrameworkWebConstant.CONTEXT_PATH_EXPRESSION);
        this.frameworkBaseConfigProperties = frameworkBaseConfigProperties;
        this.accessLogHelper = accessLogHelper;
    }

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    /**
     * controller的响应body处理，如果响应的方法返回值为void或者为null，则参数中的body为null，
     * 但ErrorController返回null则不会经过该方法，只会经过RequestResponseBodyFilter处理
     *
     * @param body                  body
     * @param returnType            returnType
     * @param selectedContentType   selectedContentType
     * @param selectedConverterType selectedConverterType
     * @param request               request
     * @param response              response
     * @return
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
                                  ServerHttpResponse response) {
        ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) request;
        HttpServletRequest servletRequest = servletServerHttpRequest.getServletRequest();
        Object result = getBody(body, returnType, selectedContentType, selectedConverterType, request, servletRequest, response);
        Object context = servletRequest.getAttribute(FrameworkBaseConstant.REQUEST_RESPONSE_CONTEXT_KEY);
        if (context instanceof RequestResponseContext) {
            recordLog(body, servletRequest, (RequestResponseContext) context);
        }
        return result;
    }

    private void recordLog(Object result, HttpServletRequest servletRequest, RequestResponseContext context) {
        if (null == context) {
            log.error("request response context is null.");
            return;
        }
        RequestConfigProperties requestConfigProperties = frameworkBaseConfigProperties.getRequest();
        ResponseConfigProperties responseConfigProperties = frameworkBaseConfigProperties.getResponse();
        int requestMaxLen = Math.max(requestConfigProperties.getRecordLocalMaxLength(), requestConfigProperties.getRecordPersistenceMaxLength());
        int responseMaxLen = Math.max(responseConfigProperties.getRecordLocalMaxLength(), responseConfigProperties.getRecordPersistenceMaxLength());
        try {
            ServletInputStream inputStream = servletRequest.getInputStream();
            byte[] bytes = IoUtil.toLimitByteArray(inputStream, requestMaxLen);
            context.setRequestBody(new String(bytes, servletRequest.getCharacterEncoding()));
        } catch (Exception e) {
            log.error("read request body error.");
        }
        if (null != result) {
            if (result instanceof String) {
                String resultString = (String) result;
                context.setResponseBody(getRecordString(responseMaxLen, resultString));
            } else {
                context.setResponseBody(getRecordString(responseMaxLen, JsonObject.toString(result)));
            }
        }
        accessLogHelper.asyncRecordAccessLog(context);
    }

    private String getRecordString(int responseMaxLen, String resultString) {
        if (resultString.length() > responseMaxLen) {
            return resultString.substring(0, responseMaxLen);
        }
        return resultString;
    }

    private Object getBody(Object body, MethodParameter returnType, MediaType selectedContentType,
                           Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
                           HttpServletRequest servletRequest, ServerHttpResponse response) {
        boolean isRpcRequest = HttpServletUtil.isRpcRequest(servletRequest);

        // 如果为RPC请求的响应，只将框架响应放入响应的header中
        if (isRpcRequest && !response.getHeaders().containsKey(FrameworkCoreConstant.FRAMEWORK_RESPONSE_KEY)) {
            Object frameworkResponse = servletRequest.getAttribute(FrameworkCoreConstant.FRAMEWORK_RESPONSE_KEY);
            if (null != frameworkResponse) {
                addFrameworkResponseToHeader(response, (Response) frameworkResponse);
                return body;
            }
            addFrameworkResponseToHeader(response, Response.success());
            return body;
        }

        // 如果用户需要自定义响应，则直接调用用户自定义响应
        if (!VerifyUtil.isEmpty(responseBodyAdviceServices)) {
            String methodName = request.getMethodValue();
            String uriPath = request.getURI().getPath();
            for (IResponseBodyAdviceService responseBodyAdviceService : responseBodyAdviceServices) {
                if (responseBodyAdviceService.isMatch(contextPath, methodName, uriPath, body)) {
                    return responseBodyAdviceService.getResponseBody(body);
                }
            }
        }
        if (body instanceof Response) {
            return body;
        }
        return Response.success(body);
    }

    private void addFrameworkResponseToHeader(ServerHttpResponse response, Response frameworkResponse) {
        if (!frameworkResponse.isSuccess()) {
            log.error("respond error response, src response body: {}", JsonObject.toString(frameworkResponse));
        }
        try {
            ServletServerHttpResponse servletServerHttpResponse = (ServletServerHttpResponse) response;
            servletServerHttpResponse.getServletResponse()
                    .setHeader(FrameworkCoreConstant.FRAMEWORK_RESPONSE_KEY,
                            URLEncoder.encode(JsonObject.toString(frameworkResponse),
                                    StandardCharsets.UTF_8.name()));
        } catch (UnsupportedEncodingException e) {
            log.error("encoding error.", e);
        }
    }
}
